package com.coder520.user.service;

import com.coder520.common.constants.Constants;
import com.coder520.common.exception.MamaBuyException;
import com.coder520.user.dao.UserMapper;
import com.coder520.user.entity.User;
import com.coder520.user.entity.UserElement;
import lombok.extern.slf4j.Slf4j;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

/**
 * @author wsq
 *         Created by wsq on 2018-07-21 17:34.
 * @desc 用户业务实现类
 */
@Service("userService")
@Slf4j
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;
    @Autowired
    private BCryptPasswordEncoder passwordEncoder;
    @Autowired
    private CuratorFramework curatorFramework;

    /**
     * 用户登陆的实现
     *
     * @param user 用户信息
     * @return 用户视图
     */
    @Override
    public UserElement login(User user) {
        UserElement ue = null;
        //查询用户email
        User existUser = userMapper.selectByEmail("");
        //因为你分表了3个  他不知道查那个表通过邮箱正常情况是查不到的  但是用了shardingJDBC是可以通过轮询查到的，但是速度慢
        //uuid 是全局不重复的多个表都不存在重复
        if (existUser == null) {
            //被全局异常处理器检查到
            throw new MamaBuyException("用户不存在");
        } else {
            //校验密码  第一个参数:用户传入的密码  第二个参数：用户本身设置的密码
            boolean result = passwordEncoder.matches(user.getPassword(), existUser.getPassword());
            if (!result) {
                //密码错误
                throw new MamaBuyException("密码错误");
            } else {
                //验证通过 复制ue
                ue = new UserElement();
                ue.setEmail(existUser.getEmail());
                ue.setUserId(existUser.getId());
                ue.setUuid(existUser.getUuid());
                ue.setNickname(existUser.getNickname());
            }
        }
        return ue;
    }

    /**
     * 用户注册防止用户重复注册 首先查下该用户是否存在 但是在多线程环境下是有问题的
     * 多个用户同时注册防止多个用户同名  如果是单表的话在数据库将该字段设置为唯一的但是多表不行
     * 使用分布式锁牺牲下效率让业务排队从技术层面考虑 zookeeper分布式锁  redis分布式锁（效率高）
     *
     * @param user
     */
    @Override
    public void registerUser(User user) throws Exception {
        //操作分布式锁
        InterProcessMutex lock = null;
        try {
            //实现业务逻辑
            //上分布式锁
            lock=new InterProcessMutex(curatorFramework, Constants.USER_REGISTER_DISTRIBUTE_LOCK_PATH);
            //设置3秒的超时时间  无限循环直到成功最好加入次数控制
            boolean retry=true;
            do {
                if (lock.acquire(3000, TimeUnit.MILLISECONDS)){
                    //查询重复用户
                    User repeateUser=userMapper.selectByEmail(user.getEmail());
                    if(repeateUser!=null){
                        throw new MamaBuyException("用户邮箱重复");
                    }
                    //检查两次密码是否一致
                    user.setPassword(passwordEncoder.encode(user.getPassword()));
                    user.setNickname("码码购用户"+user.getEmail());
                    userMapper.insertSelective(user);
                    retry=false;
                }
            }while (retry);

        } catch (Exception e) {
            log.error("用户注册异常"+e);
            throw e;
        } finally {
            if (null != lock) {
                try {
                    //释放锁
                    lock.release();
                    log.info(user.getEmail()+Thread.currentThread().getName()+"释放锁");
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }

    }
}
